%% Author: vivimice
%% Created: Feb 16, 2012
%% Description: TODO: Add description to tstring
-module(tstring).

%%
%% Include files
%%
-include("tchar.hrl").
-include("tstring.hrl").

%%
%% Exported Functions
%%
-export([new/0, value_of/1, value_of/2, repeat/2, equals/2]).
-export([append/2, head/1, last/1, length/1]).
-export([to_binary/1, substring/2, substring/3, crop_to_width/2, pad_to_width/4]).

%%
%% API Functions
%%

% new tstring
new() ->
	#tstring{tchar_list = [], length = 0, width = 0}.

% value of
value_of(Integer) when erlang:is_integer(Integer) ->
	value_of(integer_to_list(Integer));
value_of(Float) when is_float(Float) ->
	value_of(float_to_list(Float));
value_of(Atom) when is_atom(Atom) ->
	value_of(atom_to_list(Atom));
value_of(TChar) when ?IS_TCHAR(TChar) ->
	tchar_list_to_tstring([TChar]);
value_of(String) ->
	TCharList = tchar_list_of_string(String, []),
	tchar_list_to_tstring(TCharList).

value_of(Integer, Base) when is_integer(Integer), Base >= 2, Base =< 36 ->
	value_of(integer_to_list(Integer, Base)).

%% repeat
repeat(TChar, 0) when not ?IS_TCHAR(TChar) ->
	erlang:error(badarg);
repeat(_TChar, 0) ->
	new();
repeat(TChar, 1) ->
	value_of(TChar);
repeat(TChar, Count) ->
	repeat_i(TChar, [], Count).

%% equals
equals(TString1, TString2) when ?IS_TSTRING(TString1), ?IS_TSTRING(TString2) ->
	TString1#tstring.length == TString2#tstring.length andalso
	TString1#tstring.width == TString2#tstring.width andalso
	tchar_list_equals(TString1#tstring.tchar_list, TString2#tstring.tchar_list).

%% to binary
to_binary(TString) when ?IS_TSTRING(TString) ->
	tchar_list_to_binary(TString#tstring.tchar_list, <<>>).

% get length
length(TString) when ?IS_TSTRING(TString) ->
	?TSTRING_LENGTH(TString).

% append
append(TString, TChar) when ?IS_TSTRING(TString), ?IS_TCHAR(TChar) ->
	#tstring{tchar_list = TString#tstring.tchar_list ++ [TChar], 
			 length = TString#tstring.length + 1,
			 width = TString#tstring.width + ?TCHAR_WIDTH(TChar)};
append(TString1, TString2) when ?IS_TSTRING(TString1), ?IS_TSTRING(TString2) ->
	#tstring{tchar_list = TString1#tstring.tchar_list ++ TString2#tstring.tchar_list,
			 length = TString1#tstring.length + TString2#tstring.length,
			 width = TString1#tstring.width + TString2#tstring.width};
append(TString, Term) ->
	append(TString, value_of(Term)).

% head tchar of tstring
head(TString) when ?IS_TSTRING(TString) ->
	[TChar|_] = TString#tstring.tchar_list,
	TChar.

% last tchar of tstring
last(TString) when ?IS_TSTRING(TString) ->
	lists:last(TString#tstring.tchar_list).

% substring
substring(TString, Len) when ?IS_TSTRING(TString) ->
	tchar_list_to_tstring(lists:sublist(TString#tstring.tchar_list, Len)).

% substring, note that index starts from 0
substring(TString, Start, Len) when ?IS_TSTRING(TString) ->
	tchar_list_to_tstring(lists:sublist(TString#tstring.tchar_list, Start + 1, Len)).

% crop to width
crop_to_width(TString, Width) when ?IS_TSTRING(TString) ->
	tchar_list_to_tstring(crop_to_width_i([], TString#tstring.tchar_list, Width)).

% pad to width
pad_to_width(TString, Width, TCharPad, Side) 
  when ?IS_TSTRING(TString), 
	   erlang:is_number(Width),
	   ?IS_TCHAR(TCharPad),
	   (Side == left) or (Side == right) ->
	SrcWidth = ?TSTRING_WIDTH(TString),
	RepeatCount = utils:floor((Width - SrcWidth) / ?TCHAR_WIDTH(TCharPad)),
	if 
		RepeatCount > 0 ->
			case Side of
				left ->
					tstring:append(tstring:repeat(TCharPad, RepeatCount), TString);
				right ->
					tstring:append(TString, tstring:repeat(TCharPad, RepeatCount))
			end;
		true ->
			TString
	end.
	

%%
%% Local Functions
%%

% create tstring from list of tchars
tchar_list_to_tstring(TCharList) ->
	#tstring{tchar_list = TCharList, 
			 length = erlang:length(TCharList), 
			 width = get_tchar_list_width(TCharList)
			}.

tchar_list_of_string([], TCharList) ->
	TCharList;
tchar_list_of_string([Head|Rest], TCharList) 
  when erlang:is_number(Head), Head < 128 ->
	TChar = tchar:new_ansi(Head),
	tchar_list_of_string(Rest, TCharList ++ [TChar]);
tchar_list_of_string([Head|Rest], TCharList) 
  when erlang:is_number(Head) ->
	TChar = tchar:new_mb(<<Head:16>>),
	tchar_list_of_string(Rest, TCharList ++ [TChar]);
tchar_list_of_string(_1, _2) ->
	logger:error("Bad args: ~w ~w", [_1, _2]),
	erlang:error(badarg).

repeat_i(_TChar, TCharList, 0) ->
	tchar_list_to_tstring(TCharList);
repeat_i(TChar, TCharList, Size) ->
	repeat_i(TChar, [TChar|TCharList], Size - 1).

tchar_list_equals([], []) ->
	true;
tchar_list_equals([], [_|_]) ->
	false;
tchar_list_equals([_|_], []) ->
	false;
tchar_list_equals([TChar1|Rest1], [TChar2|Rest2]) ->
	Equals = tchar:equals(TChar1, TChar2),
	if 
		not Equals ->
			false;
		true ->
			tchar_list_equals(Rest1, Rest2)
	end.

get_tchar_list_width(TCharList) ->
	get_tchar_list_width(TCharList, 0).

get_tchar_list_width([], Length) ->
	Length;
get_tchar_list_width([TChar|Rest], Length) ->
	get_tchar_list_width(Rest, Length + ?TCHAR_WIDTH(TChar)).

tchar_list_to_binary([], Binaries) ->
	Binaries;
tchar_list_to_binary([TChar|Rest], Binaries) ->
	tchar_list_to_binary(Rest, <<Binaries/binary, (tchar:to_binary(TChar))/binary>>).

crop_to_width_i(TrimedTCharList, _, 0) ->
	TrimedTCharList;
crop_to_width_i(TrimedTCharList, [], Width) when Width > 0 ->
	TrimedTCharList;
crop_to_width_i(TrimedTCharList, [TChar|Rest], Width) when Width > 0 ->
	TCharWidth = ?TCHAR_WIDTH(TChar),
	if
		Width - TCharWidth < 0 ->
			TrimedTCharList;
		true ->
			crop_to_width_i(TrimedTCharList ++ [TChar], Rest, Width - TCharWidth)
	end.
